package net.jgrm.redisadvanced.lock;

import lombok.extern.slf4j.Slf4j;
import net.jgrm.redisadvanced.config.RedisAdvancedProperties;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;

/**
 * Redis分布式锁服务实现类
 * 使用Redisson实现分布式锁
 */
@Slf4j
//@Service
public class RedisLockServiceImpl implements RedisLockService {

    //    @Autowired
    private RedissonClient redissonClient;

    //    @Autowired
    private RedisAdvancedProperties redisAdvancedProperties;

    public RedisLockServiceImpl(RedissonClient redissonClient, RedisAdvancedProperties redisAdvancedProperties) {
        this.redissonClient = redissonClient;
        this.redisAdvancedProperties = redisAdvancedProperties;
    }

    // 锁键前缀
    private static final String LOCK_PREFIX = "lock:";
    // 可重入锁键前缀
    private static final String REENTRANT_LOCK_PREFIX = "lock:reentrant:";
    // 公平锁键前缀
    private static final String FAIR_LOCK_PREFIX = "lock:fair:";
    // 读写锁键前缀
    private static final String READ_WRITE_LOCK_PREFIX = "lock:readwrite:";

    /**
     * 添加前缀到锁键名
     */
    private String addPrefix(String prefix, String lockKey) {
        return redisAdvancedProperties.getKeyPrefix() + prefix + lockKey;
    }

    @Override
    public boolean lock(String lockKey) {
        try {
            String key = addPrefix(LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getLock(key);
            lock.lock();
            log.debug("Acquired lock: {}", lockKey);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire lock", e);
        }
    }

    @Override
    public boolean lock(String lockKey, long expireTime, TimeUnit timeUnit) {
        try {
            String key = addPrefix(LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getLock(key);
            lock.lock(expireTime, timeUnit);
            log.debug("Acquired lock with expire time: {}, expireTime: {}, timeUnit: {}",
                    lockKey, expireTime, timeUnit);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire lock with expire time: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire lock with expire time", e);
        }
    }

    /**
     * 尝试获取锁，带过期时间 如果过期时间>0，则不在使用看门狗进行锁续期
     *
     * @param lockKey   锁的键名
     * @param waitTime  等待时间
     * @param leaseTime 过期时间
     * @param timeUnit  时间单位
     * @return 是否获取锁成功
     * @throws InterruptedException 线程中断异常
     */
    @Override
    public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit timeUnit) throws InterruptedException {
        try {
            String key = addPrefix(LOCK_PREFIX, lockKey);
            log.debug("Trying to acquire lock: {}, with waitTime: {}, leaseTime: {}", lockKey, waitTime, leaseTime);

            // 获取锁实例
            RLock lock = redissonClient.getLock(key);

            // 直接使用用户提供的参数，不做修改
            boolean acquired = lock.tryLock(waitTime, leaseTime, timeUnit);

            if (acquired) {
                log.info("Successfully acquired lock: {}, thread: {}", lockKey, Thread.currentThread().getName());
            } else {
                log.info("Failed to acquire lock: {}, lock is already held by another thread", lockKey);
            }

            return acquired;
        } catch (InterruptedException e) {
            log.error("Interrupted while trying to acquire lock: {}", lockKey, e);
            Thread.currentThread().interrupt();
            throw e;
        } catch (Exception e) {
            log.error("Failed to try lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to try lock", e);
        }
    }

    /**
     * 不指定leaseTime，这样看门狗会进行锁续期
     *
     * @param lockKey  锁键名
     * @param waitTime 等待时间
     * @param timeUnit 时间单位
     * @return 是否获取锁成功
     * @throws InterruptedException 线程中断异常
     */
    @Override
    public boolean tryLock(String lockKey, long waitTime, TimeUnit timeUnit) throws InterruptedException {
        try {
            String key = addPrefix(LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getLock(key);
            boolean acquired = lock.tryLock(waitTime, timeUnit);
            log.debug("Try lock result: {}, lock: {}, waitTime: {}",
                    acquired, lockKey, waitTime);
            return acquired;
        } catch (InterruptedException e) {
            log.error("Interrupted while trying to acquire lock: {}", lockKey, e);
            Thread.currentThread().interrupt();
            throw e;
        } catch (Exception e) {
            log.error("Failed to try lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to try lock", e);
        }
    }

    @Override
    public boolean unlock(String lockKey) {
        try {
            String key = addPrefix(LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getLock(key);

            // 检查当前线程是否持有锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.debug("Released lock: {}, thread: {}", lockKey, Thread.currentThread().getName());
                return true;
            } else {
                log.warn("Current thread does not hold the lock: {}", lockKey);
                return false;
            }
        } catch (IllegalMonitorStateException e) {
            // 捕获Redisson的IllegalMonitorStateException，常见于重复解锁或未持有锁的线程尝试解锁
            log.error("Illegal attempt to unlock lock: {}, error: {}", lockKey, e.getMessage());
            return false;
        } catch (Exception e) {
            log.error("Failed to release lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to release lock", e);
        }
    }

    @Override
    public boolean isLocked(String lockKey) {
        try {
            String key = addPrefix(LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getLock(key);
            boolean locked = lock.isLocked();
            log.debug("Lock status: {}, lock: {}", locked, lockKey);
            return locked;
        } catch (Exception e) {
            log.error("Failed to check lock status: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to check lock status", e);
        }
    }

    // 可重入锁相关方法
    @Override
    public boolean lockReentrant(String lockKey) {
        try {
            String key = addPrefix(REENTRANT_LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getLock(key);
            lock.lock();
            log.debug("Acquired reentrant lock: {}", lockKey);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire reentrant lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire reentrant lock", e);
        }
    }

    @Override
    public boolean lockReentrant(String lockKey, long expireTime, TimeUnit timeUnit) {
        try {
            String key = addPrefix(REENTRANT_LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getLock(key);
            lock.lock(expireTime, timeUnit);
            log.debug("Acquired reentrant lock with expire time: {}, expireTime: {}, timeUnit: {}",
                    lockKey, expireTime, timeUnit);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire reentrant lock with expire time: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire reentrant lock with expire time", e);
        }
    }

    @Override
    public boolean tryLockReentrant(String lockKey, long waitTime, long expireTime, TimeUnit timeUnit) throws InterruptedException {
        try {
            String key = addPrefix(REENTRANT_LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getLock(key);
            boolean acquired = lock.tryLock(waitTime, expireTime, timeUnit);
            log.debug("Try reentrant lock result: {}, lock: {}, waitTime: {}, expireTime: {}",
                    acquired, lockKey, waitTime, expireTime);
            return acquired;
        } catch (InterruptedException e) {
            log.error("Interrupted while trying to acquire reentrant lock: {}", lockKey, e);
            Thread.currentThread().interrupt();
            throw e;
        } catch (Exception e) {
            log.error("Failed to try reentrant lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to try reentrant lock", e);
        }
    }

    @Override
    public boolean unlockReentrant(String lockKey) {
        try {
            String key = addPrefix(REENTRANT_LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getLock(key);

            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.debug("Released reentrant lock: {}", lockKey);
                return true;
            } else {
                log.warn("Current thread does not hold the reentrant lock: {}", lockKey);
                return false;
            }
        } catch (IllegalMonitorStateException e) {
            log.error("Illegal attempt to unlock reentrant lock: {}, error: {}", lockKey, e.getMessage());
            return false;
        } catch (Exception e) {
            log.error("Failed to release reentrant lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to release reentrant lock", e);
        }
    }

    // 公平锁相关方法
    @Override
    public boolean lockFair(String lockKey) {
        try {
            String key = addPrefix(FAIR_LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getFairLock(key);
            lock.lock();
            log.debug("Acquired fair lock: {}", lockKey);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire fair lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire fair lock", e);
        }
    }

    @Override
    public boolean lockFair(String lockKey, long expireTime, TimeUnit timeUnit) {
        try {
            String key = addPrefix(FAIR_LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getFairLock(key);
            lock.lock(expireTime, timeUnit);
            log.debug("Acquired fair lock with expire time: {}, expireTime: {}, timeUnit: {}",
                    lockKey, expireTime, timeUnit);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire fair lock with expire time: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire fair lock with expire time", e);
        }
    }

    @Override
    public boolean tryLockFair(String lockKey, long waitTime, long expireTime, TimeUnit timeUnit) throws InterruptedException {
        try {
            String key = addPrefix(FAIR_LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getFairLock(key);
            boolean acquired = lock.tryLock(waitTime, expireTime, timeUnit);
            log.debug("Try fair lock result: {}, lock: {}, waitTime: {}, expireTime: {}",
                    acquired, lockKey, waitTime, expireTime);
            return acquired;
        } catch (InterruptedException e) {
            log.error("Interrupted while trying to acquire fair lock: {}", lockKey, e);
            Thread.currentThread().interrupt();
            throw e;
        } catch (Exception e) {
            log.error("Failed to try fair lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to try fair lock", e);
        }
    }

    @Override
    public boolean unlockFair(String lockKey) {
        try {
            String key = addPrefix(FAIR_LOCK_PREFIX, lockKey);
            RLock lock = redissonClient.getFairLock(key);

            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.debug("Released fair lock: {}", lockKey);
                return true;
            } else {
                log.warn("Current thread does not hold the fair lock: {}", lockKey);
                return false;
            }
        } catch (IllegalMonitorStateException e) {
            log.error("Illegal attempt to unlock fair lock: {}, error: {}", lockKey, e.getMessage());
            return false;
        } catch (Exception e) {
            log.error("Failed to release fair lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to release fair lock", e);
        }
    }

    // 读写锁相关方法
    @Override
    public boolean lockRead(String lockKey) {
        try {
            String key = addPrefix(READ_WRITE_LOCK_PREFIX, lockKey);
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
            readWriteLock.readLock().lock();
            log.debug("Acquired read lock: {}", lockKey);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire read lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire read lock", e);
        }
    }

    @Override
    public boolean lockRead(String lockKey, long expireTime, TimeUnit timeUnit) {
        try {
            String key = addPrefix(READ_WRITE_LOCK_PREFIX, lockKey);
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
            readWriteLock.readLock().lock(expireTime, timeUnit);
            log.debug("Acquired read lock with expire time: {}, expireTime: {}, timeUnit: {}",
                    lockKey, expireTime, timeUnit);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire read lock with expire time: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire read lock with expire time", e);
        }
    }

    @Override
    public boolean tryLockRead(String lockKey, long waitTime, long expireTime, TimeUnit timeUnit) throws InterruptedException {
        try {
            String key = addPrefix(READ_WRITE_LOCK_PREFIX, lockKey);
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
            boolean acquired = readWriteLock.readLock().tryLock(waitTime, expireTime, timeUnit);
            log.debug("Try read lock result: {}, lock: {}, waitTime: {}, expireTime: {}",
                    acquired, lockKey, waitTime, expireTime);
            return acquired;
        } catch (InterruptedException e) {
            log.error("Interrupted while trying to acquire read lock: {}", lockKey, e);
            Thread.currentThread().interrupt();
            throw e;
        } catch (Exception e) {
            log.error("Failed to try read lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to try read lock", e);
        }
    }

    @Override
    public boolean unlockRead(String lockKey) {
        try {
            String key = addPrefix(READ_WRITE_LOCK_PREFIX, lockKey);
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
            RLock readLock = readWriteLock.readLock();

            if (readLock.isHeldByCurrentThread()) {
                readLock.unlock();
                log.debug("Released read lock: {}", lockKey);
                return true;
            } else {
                log.warn("Current thread does not hold the read lock: {}", lockKey);
                return false;
            }
        } catch (IllegalMonitorStateException e) {
            log.error("Illegal attempt to unlock read lock: {}, error: {}", lockKey, e.getMessage());
            return false;
        } catch (Exception e) {
            log.error("Failed to release read lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to release read lock", e);
        }
    }

    @Override
    public boolean lockWrite(String lockKey) {
        try {
            String key = addPrefix(READ_WRITE_LOCK_PREFIX, lockKey);
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
            readWriteLock.writeLock().lock();
            log.debug("Acquired write lock: {}", lockKey);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire write lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire write lock", e);
        }
    }

    @Override
    public boolean lockWrite(String lockKey, long expireTime, TimeUnit timeUnit) {
        try {
            String key = addPrefix(READ_WRITE_LOCK_PREFIX, lockKey);
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
            readWriteLock.writeLock().lock(expireTime, timeUnit);
            log.debug("Acquired write lock with expire time: {}, expireTime: {}, timeUnit: {}",
                    lockKey, expireTime, timeUnit);
            return true;
        } catch (Exception e) {
            log.error("Failed to acquire write lock with expire time: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to acquire write lock with expire time", e);
        }
    }

    @Override
    public boolean tryLockWrite(String lockKey, long waitTime, long expireTime, TimeUnit timeUnit) throws InterruptedException {
        try {
            String key = addPrefix(READ_WRITE_LOCK_PREFIX, lockKey);
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
            boolean acquired = readWriteLock.writeLock().tryLock(waitTime, expireTime, timeUnit);
            log.debug("Try write lock result: {}, lock: {}, waitTime: {}, expireTime: {}",
                    acquired, lockKey, waitTime, expireTime);
            return acquired;
        } catch (InterruptedException e) {
            log.error("Interrupted while trying to acquire write lock: {}", lockKey, e);
            Thread.currentThread().interrupt();
            throw e;
        } catch (Exception e) {
            log.error("Failed to try write lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to try write lock", e);
        }
    }

    @Override
    public boolean unlockWrite(String lockKey) {
        try {
            String key = addPrefix(READ_WRITE_LOCK_PREFIX, lockKey);
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
            RLock writeLock = readWriteLock.writeLock();

            if (writeLock.isHeldByCurrentThread()) {
                writeLock.unlock();
                log.debug("Released write lock: {}", lockKey);
                return true;
            } else {
                log.warn("Current thread does not hold the write lock: {}", lockKey);
                return false;
            }
        } catch (IllegalMonitorStateException e) {
            log.error("Illegal attempt to unlock write lock: {}, error: {}", lockKey, e.getMessage());
            return false;
        } catch (Exception e) {
            log.error("Failed to release write lock: {}, error: {}", lockKey, e.getMessage(), e);
            throw new RuntimeException("Failed to release write lock", e);
        }
    }
}
